package com.wider.foundation.channel;


import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.mail.MessagingException;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimePartDataSource;
import javax.mail.internet.MimeUtility;
import javax.servlet.http.HttpServletRequest;

/**
 * Utility class for handling incoming HTML form data in MIME-Multipart format.
 *
 * @author William
 */
public class MultipartFormData {
    static final int KB = 1024;
    static final int MB = 1024 * KB;
   
    private int     partlimit;
    private int     postlimit;
    private String  mpheader;
    private Logger  logger;
    private boolean mapvalid;
    private Map     parammap;
    private Map     filesmap;
   
    private MimeMultipart           multipart;
    private HttpServletRequest      httpreq;

     /**
     * Default constructor for creating object representing the piece of the Multipart/form-data from the HTTP POST.
     * The size limit for each uploaded file will be 2MB as default.
     * @param request   valid reference of the incoming HTTP request
     */
    public MultipartFormData(HttpServletRequest httprequest) throws IOException{
        this(httprequest, 6*MB, 2*MB);
    }

     /**
     * Ultimate constructor for creating object representing the piece of the Multipart/form-data from the HTTP POST.
     * The size limit for each file will be explicitly specified by the user
     * @param request   valid reference of the incoming HTTP request
     * @param post_limit    limit in MB for the data in each HTTP POST
     * @param part_limit    limit in MB for each part in the multipart/form-data of the HTTP POST
     */
    public MultipartFormData(HttpServletRequest httprequest, int post_limit, int part_limit) throws IOException{
        if (!isMultipartFormData(httprequest)) {
            throw new IOException("Posted content isn't in type of multipart/form-data");
        }

        postlimit   = post_limit;
        partlimit   = part_limit;
        httpreq     = httprequest;
        mpheader    = httpreq.getContentType();
        mapvalid    = false;
        parammap    = new HashMap<String, Object>(); //<ctrl-name, content> | <"files", List of file-names>
        filesmap    = new HashMap<String, Object>(); //<file-name, partbody>

        //Get my logger for future use
        logger      = Logger.getLogger(this.getClass().getName());
       
        //Get MIME Multipart structure from the POSTed content, and save it in private member 'multipart'
        multipart   = getMimeMultipart();

    }
    public MultipartFormData(InputStream inp, int post_limit, int part_limit) throws IOException{
       

        postlimit   = post_limit;
        partlimit   = part_limit;
      //  httpreq     = httprequest;
        mpheader    = "";
        mapvalid    = false;
        parammap    = new HashMap<String, Object>(); //<ctrl-name, content> | <"files", List of file-names>
        filesmap    = new HashMap<String, Object>(); //<file-name, partbody>

        //Get my logger for future use
        logger      = Logger.getLogger(this.getClass().getName());
       
        //Get MIME Multipart structure from the POSTed content, and save it in private member 'multipart'
        //multipart   = getMimeMultipart();
        mpheader = mpheader + "\r\n\r\n";
        SequenceInputStream mimestream = new SequenceInputStream(new ByteArrayInputStream(mpheader.getBytes()), inp);
       
        try {
            MimePartDataSource mimesrc = new MimePartDataSource(new MimeBodyPart(mimestream));
            multipart = new MimeMultipart(mimesrc);
           
         } catch (Exception ex) {
            ex.printStackTrace();
            throw new IOException("Failed to construct the MIME Multipart object.");
        }
        

    }
   
    public static boolean isMultipartFormData(HttpServletRequest httprequest) throws IOException{
        try{
            return httprequest.getContentType().toLowerCase().contains("multipart/form-data");
        }
        catch (Exception e){
            throw new IOException("Invalid Request reference or posted was corrupted.");
        }
    }

    public Map getParameterMap() throws IOException{
        if (!mapvalid)
            genParamMap();

        return parammap;
    }


    public Object getParameter(String key) throws IOException{
        if (!mapvalid)
            genParamMap();

        return parammap.get(key);
    }

    public Enumeration getParameterNames() throws IOException{
        if (!mapvalid)
            genParamMap();

        return Collections.enumeration(parammap.keySet());
    }

    /*
    public Object[] getParameterValues(String name) throws IOException{
        if (!mapvalid)
            genParamMap();
       
        //?? this way returns the entire values set, i.e. not related to the String name
        return parammap.values().toArray();
    }
    */
   
    public int saveFile(String filename, String destination) throws IOException{
        try {
            int ret = 0;
            MimeBodyPart bodypart = (MimeBodyPart) filesmap.get(filename);
            File file = new File(destination, MimeUtility.decodeText(filename));
            bodypart.saveFile(file);
            return ++ret;
        } catch (MessagingException ex) {
            logger.log(Level.SEVERE, "JavaMail MessagingException occurs while saving file.", ex);
            throw new IOException("JavaMail MessagingException occurs while saving file.");
        }
    }

    public int saveFiles(List filenames, String destination) throws IOException{
        int ret = 0;

        Iterator filenameit = filenames.iterator();
        while(filenameit.hasNext()){
            String filename = (String) filenameit.next();
            try {
                MimeBodyPart bodypart = (MimeBodyPart) filesmap.get(filename);
                File file = new File(destination, MimeUtility.decodeText(filename));
                bodypart.saveFile(file);
                ret++;
            } catch (MessagingException ex) {
                logger.log(Level.SEVERE, "JavaMail MessagingException occurs while saving files at file: " + filename + ".", ex);
            }
        }

        return ret;
    }

    private MimeMultipart getMimeMultipart() throws IOException{

        mpheader = mpheader + "\r\n\r\n";
        SequenceInputStream mimestream = new SequenceInputStream(new ByteArrayInputStream(mpheader.getBytes()), httpreq.getInputStream());
       
        try {
            MimePartDataSource mimesrc = new MimePartDataSource(new MimeBodyPart(mimestream));
            MimeMultipart multi = new MimeMultipart(mimesrc);
            return multi;
         } catch (Exception ex) {
            ex.printStackTrace();
            throw new IOException("Failed to construct the MIME Multipart object.");
        }
    }

    private String getPartControlName(MimeBodyPart bodypart) throws IOException{
        Enumeration headers;
        String  name        = null;
        String  stack       = null;
        String  needle      = "name=";
        char[]  delimiters  = {';', ' ', '\r', '\n'};
       
        try {
            headers = bodypart.getAllHeaderLines();
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new IOException("Exception occurs while operating on the MimeMultipart object");
        }
       
        while(headers.hasMoreElements()){
            int     index_start =  0;
            int     index_stop  = -1;

            stack = headers.nextElement().toString().toLowerCase();
            index_start = stack.indexOf(needle);
            if (index_start < 0)
                continue;
            index_start += needle.length();

            int j = 0;
            while ((index_stop < 0) && (j < delimiters.length)){
                index_stop = stack.indexOf(delimiters[j], index_start);
                j++;
            }
            if (index_stop < 0)
                index_stop = stack.length();


            name = stack.substring(index_start, index_stop);
            break; //no need to iterate to the next header element;
        }
        return name;
    }

    private void genParamMap() throws IOException{
        MimeBodyPart    bodypart    = null;
        String          ctrlname    = null;
        String          disposition = null;

        try {
            //Iterate each bodypart within the multipart to fill up the Map of param and files
            for (int i = 0; i < multipart.getCount(); i++) {
                bodypart    = (MimeBodyPart) multipart.getBodyPart(i);
                ctrlname    = getPartControlName(bodypart);
                disposition = bodypart.getDisposition();

                if (disposition.equalsIgnoreCase("file")){
                    String filename = bodypart.getFileName();
                    ArrayList<String> filenames = null;
                    if (null == (filenames = (ArrayList<String>) parammap.get(ctrlname))){
                        filenames = new ArrayList<String>();
                    }
                    filenames.add(filename);
                    parammap.put(ctrlname, filenames);
                    filesmap.put(filename, bodypart);                    
                }
                else if(disposition.equalsIgnoreCase("vector")){
                	parammap.put(ctrlname, bodypart.getContent());
                }
                else{
                    parammap.put(ctrlname, bodypart.getContent().toString());
                }
            }

            //Set the valid flag so that this method will no longer be called.
            mapvalid = true;

        } catch (Exception ex) {
            throw new IOException("Exception occurs while operating on the MimeMultipart object");
        }
    }
}


